<?php
// +----------------------------------------------------------------------
// | Description: 公共模型,所有模型都可继承此模型，基于RESTFUL CRUD操作
// +----------------------------------------------------------------------
// | Author: linchuangbin <linchuangbin@honraytech.com>
// +----------------------------------------------------------------------

namespace app\common\model;

use think\Model;

class Base extends Model
{
	protected $_AllowRecache=false;
	protected $_AllowEnable=true;
	protected $_AllowGetChild=true;
	protected $_AllowDelete=true;
	protected $_AllowSiteInsulate=true;

	/**
	 * the field cannot be insert by invoke createData()
	 * @var null|Array
	 */
	protected $_RejectCreate=null;

	/**
	 * the field cannot be update by invoke createData()
	 * @var null|Array
	 */
	protected $_RejectUpdate=null;

	/**
	 * this function is used for reset the cache after update data
	 * Tips: this function is a abstract function, when you set $_AllowPreprocess to true,
	 * you have to implement it, otherwise, you can ignore it
	 * @return no retrnn val
	 */
	//protected function recache();

	/**
	 * this function used for get the web site white list to allow the operate
	 * Tips: this function is a abstract function, when you set $_AllowPreprocess to true,
	 * you have to implement it, otherwise, you can ignore it
	 * @return Array return a set of site_id, which allow to be gotten.
	 */
	// protected function _allow_Sites();

	/**
	 * A function will be called before the getDataList exec,It could be used for preprocessing the data,
	 * This function is a virtual function, you can define it or not.
	 * the parameter is a reference, you must be carefully to modify the parameter in this function,
	 * if the function execute successfully, you must return true,and if the function execute failed, you can just return the reason of the failure, it will be a string type
	 * It will be called in the getDataList() and getDataListCount()
	 * @param  array &$condition the filter condition will be applied to search
	 * @return true|string      you must return a true or a string
	 */
	// protected function _before_getDataList(&$condition);

	/**
	 * A function will be called after the getDataList exec, You can use this function to do some
	 * This function is a virtual function, you can define it or not.
	 *
	 * if the function execute successfully, you must return true,and if the function execute failed, you can just return the reason of the failure, it will be a string type
	 * @param  array &$result
	 * @param  boolean $onlyId
	 * @return true|string      you must return a true or a string
	 */
	//protected function _after_getDataList(&$result, $onlyId);


	/**
	 * this function is used for preprocessing the data before exec createData()
	 * This function is a virtual function, you can define it or not.
	 * @param $param       the data will be save
	 * @return Boolean     if the function execute successfully, you must return true,and if the function execute failed, you can just return the reason of the failure, it will be a string type
	 */
	//protected function _before_createData(&$param);


	/**
	 * this function is used for processing the data after exec createData()
	 * This function is a virtual function, you can define it or not.
	 * @param $param       the data had been save
	 * @return Boolean     if the function execute successfully, you must return true,and if the function execute failed, you can just return the reason of the failure, it will be a string type
	 */
	//protected function _after_createData(&$param);


	/**
	 * this function is used for preprocessing the data before exec updateDataById()
	 * This function is a virtual function, you can define it or not.
	 * @param $param       the data will be save
	 * @return Boolean     if the function execute successfully, you must return true,and if the function execute failed, you can just return the reason of the failure, it will be a string type
	 */
	//protected function _before_updateDataById(&$param);


	/**
	 * this function is used for replacing the default update method,when you define this function ,you have to update all the data by yourself, be carefully to use this.
	 * This function is a virtual function, you can define it or not.
	 * @param $param       the data had been save
	 * @return Boolean     if the function execute successfully, you must return true,and if the function execute failed, you can just return the reason of the failure, it will be a string type
	 */
	//protected function _doing_updateDataById(&$param);


	/**
	 * this function is used for processing the data after exec updateDataById()
	 * This function is a virtual function, you can define it or not.
	 * @param $param       the data had been save
	 * @return Boolean     if the function execute successfully, you must return true,and if the function execute failed, you can just return the reason of the failure, it will be a string type
	 */
	//protected function _after_updateDataById(&$param);

	/**
	 * this function is used for replacing the default delete method,when you define this function ,you have to delete all the data by yourself, be carefully to use this.
	 * This function is a virtual function, you can define it or not.
	 * if the parameter(delSon)  of delDataById() be set to true, this function may be called twice.
	 * @param  array $delIds the ids will be deleted
	 * @return Boolean     if the function execute successfully, you must return true,and if the function execute failed, you can just return the reason of the failure, it will be a string type
	 */
	//protected function _doing_delDataById($delIds);


	/**
	 * get data according to  $condition,
	 * this function provide three preserve condition key, there are _page,_limit,_onlyId,_order, you can use them to controller the result
	 *
	 * @param array|null $condition the condition data to filter
	 *
	 * @return Array the result meet the condition, and order by _order.
	 */
	public function getDataList($condition){
		if ($this->_AllowSiteInsulate) {
			if (!$condition)
				$condition = [];

			if (method_exists($this, '_allow_Sites')) {
				if ($sites = $this->_allow_Sites() && count($sites)) {
					$condition[] = ['site_id', 'in', $sites];
				} else {
					$condition[] = ['site_id', '=', $GLOBALS['userInfo']['site_id']];
				}
			} else {
				$condition[] = ['site_id', '=', $GLOBALS['userInfo']['site_id']];
			}
		}
		if (method_exists($this, '_before_getDataList')){
			if (($this->error = $this->_before_getDataList($condition))!==true) {
				if ($this->error == null) {
					$this->error = '后端人员写代码的时候发生了小失误（快提醒他），位置：_before_getDataList';
				}
				return false;
			}
		}

		$onlyId = false;
		$order = null;
		if (count($condition)) {
			$page = 0;
			$limit = 0;
			if (isset($condition['_page'])){
				$page = $condition['_page'];
				unset($condition['_page']);
			}
			if (isset($condition['_limit'])){
				$limit = $condition['_limit'];
				unset($condition['_limit']);
			}
			if (isset($condition['_onlyId'])){
				$onlyId = $condition['_onlyId'];
				unset($condition['_onlyId']);
			}
			if (isset($condition['_order'])){
				$order = $condition['_order'];
				unset($condition['_order']);
			}
			$query=$this->where($condition);

			if ($page && $limit)
				$query->page($page,$limit);

			if (is_array($order) && $order['field'] && $order['desc']) {
				$query->order($order['field'], $order['desc']?'desc':'asc');
			}
		}
		else
			$query=$this;

		if ($onlyId)
			$result =  $query=$query->column('id');
		else
			$result =  $query->select();

		if (method_exists($this, '_after_getDataList')){
			if (($this->error = $this->_after_getDataList($result,$onlyId))!==true) {
				if ($this->error == null) {
					$this->error = '后端人员写代码的时候发生了小失误（快提醒他），位置：_after_getDataList';
				}
				return false;
			}
		}

		return $result;
	}


	/**
	 * get number of data list, use same condition with getDataList(),
	 * this function is used for counting the number of the object will be get.
	 * @param array $condition
	 *
	 * @return int
	 */
	public function getDataListCount($condition){
		if ($this->_AllowSiteInsulate) {
			if (!$condition)
				$condition = [];
			if (method_exists($this, '_allow_Sites')) {
				if ($sites = $this->_allow_Sites() && count($sites)) {
					$condition[] = ['site_id', 'in', $sites];
				} else {
					$condition[] = ['site_id', '=', $GLOBALS['userInfo']['site_id']];
				}
			} else {
				$condition[] = ['site_id', '=', $GLOBALS['userInfo']['site_id']];
			}
		}
		if (method_exists($this, '_before_getDataList')){
			if (($this->error = $this->_before_getDataList($condition))!==true) {
				if ($this->error == null) {
					$this->error = '后端人员写代码的时候发生了小失误（快提醒他），位置：_before_getDataList';
				}
				return false;
			}
		}
		if ($condition)
			$query=$this->where($condition);
		else
			$query=$this;
		return $query->count();
	}


	/**
	 * get one resource's data by it id
	 *
	 * @param int $id id of the resource will be gotten
	 *
	 * @return array|null		the information of the specific resource
	 */
	public function getDataById($id = 0) {
		if (!$data = $this->get($id)) {
			$this->error = '找不到指定资源';
			return false;
		}
		if ($this->_AllowSiteInsulate) {
			if (method_exists($this, '_allow_Sites')) {
				if ($sites = $this->_allow_Sites() && count($sites)) {
					if (!in_array($data['site_id'], $sites)) {
						$this->error = '没有权限访问资源';
						return false;
					}
				} else {
					if ($data['site_id'] != $GLOBALS['userInfo']['site_id']) {
						$this->error = '没有权限访问资源';
						return false;
					}
				}
			} else {
				if ($data['site_id'] != $GLOBALS['userInfo']['site_id']) {
					$this->error = '没有权限访问资源';
					return false;
				}
			}
		}

		return $data;
	}


	/**
	* create resource function(RESTful)
	*
	* @param array $param 		the infomation of the new resource
	*
	* @return null|int       the resource id which be created.
	*/
	public function createData($param) {
		if ($this->_RejectCreate){
			if (is_array($this->_RejectCreate)) {
				foreach ($this->_RejectCreate as $v)
					if (isset($param[$v]))
						unset($param[$v]);
			} else {
				if ($param[$this->_RejectCreate])
						unset($param[$this->_RejectCreate]);
			}
		}

		if ($this->_AllowSiteInsulate) {
				$param['site_id'] = $GLOBALS['userInfo']['site_id'];
		}

		if (method_exists($this, '_before_createData')){
			if (($this->error = $this->_before_createData($param))!==true) {
				if ($this->error == null) {
					$this->error = '后端人员写代码的时候发生了小失误（快提醒他），位置：_before_createData';
				}
				return false;
			}
		}

		if ($this->_AllowRecache){
			if (!method_exists($this,'recache')) {
				$this->error = '资源请求重置缓存，但是后端那家伙好像又写漏了点什么(快提醒他)';
				return false;
			}
		}

		try {
			if ($this->data($param)->isUpdate(false)->allowField(true)->save()){
				if (method_exists($this, '_after_createData')){
					$param['id']=$this->id;
					if (($this->error = $this->_after_createData($param))!==true) {
						if ($this->error == null) {
							$this->error = '后端人员写代码的时候发生了小失误（快提醒他），位置：_after_createData';
						}
						return false;
					}
				}

				if ($this->_AllowRecache) {
					$this->recache();
					\think\facade\Cache::clear('ModelCache_'.$this->name);
				}
				return $this->getAttr($this->getPk());
			} else {
				$this->error = "新增失败(数据异常)";
				return false;
			}
		} catch(\Exception $e) {
			$this->rollback();
			$this->error = "新增失败(数据库异常)".$e->getMessage();
			return false;
		}
	}

	/**
	 * update resource by its id
	 *
	 * @param array $param  the new value of the
	 * @param <type> $id
	 *
	 * @return <type> boolean return whether the operator success
	 */
	public function updateDataById($param, $id = 0) {
		if (!($checkData = $this->get($id))) {
			$this->error = '更新资源失败(请使用create方式)';
			return false;
		}
		if ($this->_RejectUpdate){
			if (is_array($this->_RejectUpdate)) {
				foreach ($this->_RejectUpdate as $v)
					if (isset($param[$v]))
						unset($param[$v]);
			} else {
				if ($param[$this->_RejectUpdate])
						unset($param[$this->_RejectUpdate]);
			}
		}
		if ($this->_AllowSiteInsulate) {
			$userInfo = $GLOBALS['userInfo'];
			$sites = [$userInfo['site_id']];
			if (method_exists($this, '_allow_Sites')) {
				if ($tmp_site = $this->_allow_Sites() && count($tmp_site)) {
					$sites = $tmp_site;
				} else if ($tmp_site === null) {
					$this->error = '后端人员写代码的时候发生了小失误（快提醒他），位置：_allow_Sites';
					return false;
				}
			}
			if (in_array($checkData['site_id'], $sites)) {
				if (isset($param['site_id'])) {
					if (!in_array(0, $userInfo['gids'])) {
						$this->error = '你没有权限修改此资源的所属分站id';
						return false;
					}
				}
			} else {
				$this->error = '你没有权限修改此资源';
				return false;
			}
		}

		if (method_exists($this, '_before_updateDataById')){
			if (($this->error = $this->_before_updateDataById($param))!==true) {
				if ($this->error == null) {
					$this->error = '后端人员写代码的时候发生了小失误（快提醒他），位置：_before_updateDataById';
				}
				return false;
			}
		}

		if ($this->_AllowRecache){
			if (!method_exists($this,'recache')) {
				$this->error = '资源请求重置缓存，但是后端那家伙好像又写漏了点什么(快提醒他)';
				return false;
			}
		}

		try {
			if (method_exists($this, '_doing_updateDataById')){
				if (($this->error = $this->_doing_updateDataById($param))!==true) {
					if ($this->error == null) {
						$this->error = '后端人员写代码的时候发生了小失误（快提醒他），位置：_doing_updateDataById';
					}
					return false;
				}
			} else {
				if (!$checkData->isUpdate(true)->allowField(true)->save($param)) {
					$this->error = '更新失败(数据异常)';
					return false;
				}
			}

			if ($this->_AllowRecache) {
				$this->recache();
				\think\facade\Cache::clear('ModelCache_'.$this->name);
			}

			if (method_exists($this, '_after_updateDataById')){
				if (($this->error = $this->_after_updateDataById($param))!==true) {
					if ($this->error == null) {
						$this->error = '后端人员写代码的时候发生了小失误（快提醒他），位置：_after_updateDataById';
					}
					return false;
				}
			}
			return true;
		} catch(\Exception $e) {
			$this->error = '更新失败(数据库异常)'.$e->getMessage();
			return false;
		}
	}

	/**
	 * delete the specific resource by id, you can set $delSon to true to delete all the child node,
	 *
	 * @param  string  $id     the resource will be delete
	 * @param  boolean $delSon decide to whether or not to delete the child node of the deleted resource
	 * @return boolean         if delete successfully, return true
	 */
	public function delDataById($id = 0, $delSon = false) {
		if (!$this->_AllowDelete) {
			$this->error = '删除失败(不支持的操作)';
			return false;
		}

		if ($this->_AllowRecache){
			if (!method_exists($this,'recache')) {
				$this->error = '资源请求重置缓存，但是后端那家伙好像又写漏了点什么(快提醒他)';
				return false;
			}
		}
		$sites = null;
		if ($this->_AllowSiteInsulate) {
			$userInfo = $GLOBALS['userInfo'];
			$sites = [$userInfo['site_id']];
			if (method_exists($this, '_allow_Sites')) {
				if ($tmp_site = $this->_allow_Sites() && count($tmp_site)) {
					$sites = $tmp_site;
				} else if ($tmp_site === null) {
					$this->error = '后端人员写代码的时候发生了小失误（快提醒他），位置：_allow_Sites';
					return false;
				}
			}
			if ($data = $this->get($id)) {
				if (!in_array($data->getAttr('site_id'), $sites)) {
					$this->error = '你没有权限删除此资源';
					return false;
				}
			} else {
				$this->error = '删除失败(找不到对应资源)';
				return false;
			}
		}

		try {
			$this->startTrans();

			if ($me = method_exists($this, '_doing_delDataById')){
				if (($this->error = $this->_doing_delDataById([$id]))!==true) {
					if ($this->error == null) {
						$this->error = '后端人员写代码的时候发生了小失误（快提醒他），位置：_doing_delDataById';
					}
					return false;
				}
			} else {
				if (!$this->where($this->getPk(), $id)->delete()) {
					$this->error = '删除失败(可能找不到对应资源)';
					return false;
				}
			}

			if ($delSon && is_numeric($id)) {
				if($childIds = $this->getAllChild([$id],'pid', $sites) && !empty($childIds)) {
					if ($me) {
						if (($this->error = $this->_doing_delDataById($childIds))!==true) {
							if ($this->error == null) {
								$this->error = '后端人员写代码的时候发生了小失误（快提醒他），位置：_doing_delDataById';
							}
							return false;
						}
					} else {
						if (!$this->where($this->getPk(), 'in', $childIds)->delete()){
							$this->error = '删除子资源失败';
							return false;
						}
					}
				}
			}
			$this->commit();
			if ($this->_AllowRecache) {
				$this->recache();
				\think\facade\Cache::clear('ModelCache_'.$this->name);
			}
			return true;

		} catch(\Exception $e) {
			$this->error = '删除失败(数据库异常)'.$e->getMessage();
			$this->rollback();
			return false;
		}
	}

	/**
	 * delete a batch of resource,
	 *
	 * @param  array   $ids    the ids to be deleted
	 * @param  boolean $delSon decide to whether or not to delete the child node of the deleted resource
	 * @return boolean         if delete successfully, return true
	 */
	public function delDatas($ids = [], $delSon = false)
	{
		if (!$this->_AllowDelete) {
			$this->error = '删除失败(不支持的操作)';
			return false;
		}
		if (empty($ids)) {
			$this->error = '删除失败(没有选择id)';
			return false;
		}

		if ($this->_AllowRecache){
			if (!method_exists($this,'recache')) {
				$this->error = '资源请求重置缓存，但是后端那家伙好像又写漏了点什么(快提醒他)';
				return false;
			}
		}

		$sites = null;
		if ($this->_AllowSiteInsulate) {
			$userInfo = $GLOBALS['userInfo'];
			$sites = [$userInfo['site_id']];
			if (method_exists($this, '_allow_Sites')) {
				if ($tmp_site = $this->_allow_Sites() && count($tmp_site)) {
					$sites = $tmp_site;
				} else if ($tmp_site === null) {
					$this->error = '后端人员写代码的时候发生了小失误（快提醒他），位置：_allow_Sites';
					return false;
				}
			}
		}

		if ($delSon) {
			foreach($ids as &$v)
				if (!is_numeric($v))
					unset($v);
			$ids = array_unique($this->getAllChild($ids,'pid',$sites));
		}

		try {
			$this->startTrans();
			if ($sites) {
				$ids = $this->where([['site_id','in',$sites],['id','in',$ids]])->column('id');
			}
			if (method_exists($this, '_doing_delDataById')){
				if (($this->error = $this->_doing_delDataById($ids))!==true) {
					if ($this->error == null) {
						$this->error = '后端人员写代码的时候发生了小失误（快提醒他），位置：_doing_delDataById';
					}
					return false;
				}
			} else {
				if (!$this->where($this->getPk(), 'in', $ids)->delete()) {
					$this->error='批量删除资源失败';
					return false;
				}
			}

			$this->commit();
			if ($this->_AllowRecache) {
				$this->recache();
				\think\facade\Cache::clear('ModelCache_'.$this->name);
			}
			return true;
		} catch (\Exception $e) {
			$this->error = '删除失败(数据库异常)'.$e->getMessage();
			$this->rollback();
			return false;
		}
	}

	/**
	 * enable or disable a resource by its id, the function default to enable the resource,but you can set $status to 0 to tell the function to disable the resource
	 * @param  array   $ids       the ids will be enable or disable
	 * @param  integer $status    the status you want to set,it can choice between 0 and 1
	 * @param  boolean $enableSon
	 * @return boolean         if delete successfully, return true
	 */
	public function enableDatas($ids = [], $status = 1, $enableSon = false)
	{
		if (!$this->_AllowEnable) {
			$this->error = '启用/禁用失败(不支持的操作)';
			return false;
		}

		if (empty($ids)) {
			$this->error = '启用/禁用失败(未选择ID)';
			return false;
		}

		$sites = null;
		if ($this->_AllowSiteInsulate) {
			$userInfo = $GLOBALS['userInfo'];
			$sites = [$userInfo['site_id']];
			if (method_exists($this, '_allow_Sites')) {
				if ($tmp_site = $this->_allow_Sites() && count($tmp_site)) {
					$sites = $tmp_site;
				} else if ($tmp_site === null) {
					$this->error = '后端人员写代码的时候发生了小失误（快提醒他），位置：_allow_Sites';
					return false;
				}
			}
		}

		if ($this->_AllowRecache){
			if (!method_exists($this,'recache')) {
				$this->error = '资源请求重置缓存，但是后端那家伙好像又写漏了点什么(快提醒他)';
				return false;
			}
		}

		if ($enableSon && $status === '0') {
			foreach ($ids as &$v)
				if (!is_numeric($v))
					unset($v);
			$ids = array_unique($this->getAllChild($ids,'pid',$sites));
		}

		try {
			$this->startTrans();
			if ($sites) {
				$ids = $this->where([['site_id','in',$sites],['id','in',$ids]])->column('id');
			}
			$this->where($this->getPk(),'in',$ids)->setField('status', $status);
			$this->commit();
			if ($this->_AllowRecache) {
				$this->recache();
				\think\facade\Cache::clear('ModelCache_'.$this->name);
			}
			return true;
		} catch (\Exception $e) {
			$this->error = '操作失败(数据库异常)'.$e->getMessage();
			$this->rollback();
			return false;
		}
	}

	/**
	 * get all children by ids, the depth is set to 10(to avoid stack overflow)
	 * @param  [type] $ids        [description]
	 * @param  string $parent_key [description]
	 * @return [type]             [description]
	 */
	public function getAllChild($ids,$parent_key='pid',$allow_sites=null)
	{
		if (!$this->_AllowGetChild) {
			$this->error = '获取子孙失败(不支持的操作)';
			return false;
		}
		$key = array_sum($ids)."_".count($ids);
		$cache=cache('BaseModel_C'.$this->name.$key);
		if ($cache)
			if (array_diff($cache['keys'],$ids)==[])
				return $cache['data'];

		$pk=$this->getPk();
		$data=[];
		$new_ids=[];
		$cur_ids=$ids;
		if ($allow_sites) {
			$condition = ['site_id'=>$allow_sites];
		} else {
			$condition = [];
		}

		for ($i=0;$i<10;$i++) {
			$condition[$parent_key] = $cur_ids;
			$new_ids = $this->where($condition)->column($pk);
			if (!count($new_ids))
				break;
			$data=array_merge($data,$new_ids);
			$cur_ids=$new_ids;
		}
		cache('BaseModel_C'.$this->name.$key,['keys'=>$cur_ids,'data'=>$data],config('LOGIN_SESSION_VALID'),'ModelCache_'.$this->name);
		return $data;
	}

	public function getAllParent($ids,$parent_key='pid',$allow_sites=null){
		if (!$this->_AllowGetChild) {
			$this->error = '获取父节点失败(不支持的操作)';
			return false;
		}
		$key = array_sum($ids)."_".count($ids);
		$cache=cache('BaseModel_P'.$this->name.$key);
		if ($cache)
			if (array_diff($cache['keys'],$ids)==[])
				return $cache['data'];

		$pk=$this->getPk();
		$data=[];
		$new_ids=[];
		$cur_ids=$ids;

		if ($allow_sites) {
			$condition = ['site_id'=>$allow_sites];
		} else {
			$condition = [];
		}
		for ($i=0;$i<10;$i++) {
			$condition[$pk] = $cur_ids;
			$new_ids = $this->where($condition)->column($parent_key);
			if ($new_ids==[])
				break;
			$data=array_unique(array_merge($data,$new_ids));
			$cur_ids=$new_ids;
		}
		cache('BaseModel_P'.$this->name.$key,['keys'=>$ids,'data'=>$data],config('LOGIN_SESSION_VALID'),'ModelCache_'.$this->name);
		return $data;
	}

	public function getAllBrother($ids,$parent_key='pid',$allow_sites=null) {
		if (!$this->_AllowGetChild) {
			$this->error = '获取兄弟节点失败(不支持的操作)';
			return false;
		}
		$key = array_sum($ids)."_".count($ids);
		$cache=cache('BaseModel_B'.$this->name.$key);
		if ($cache)
			if (array_diff($cache['keys'],$ids)==[])
				return $cache['data'];

		if ($allow_sites) {
			$condition = ['site_id'=>$allow_sites,$parent_key=>$ids];
		} else {
			$condition = [$parent_key=>$ids];
		}

		$data = $this->where($condition)->column($this->getPk());
		cache('BaseModel_B'.$this->name.$key,['keys'=>$ids,'data'=>$data],config('LOGIN_SESSION_VALID'),'ModelCache_'.$this->name);
		return $data;
	}
}